异常处理和浮点管道
AP SHANTHI博士
本模块的目标是讨论异常并查看 MIPS 架构如何处理它们。我们还将讨论使管道复杂化的其他问题。
异常或中断是需要改变控制流的意外事件。不同的 ISA 使用这些术语的方式不同。异常通常是指 CPU 内出现的事件,例如未定义的操作码、溢出、系统调用等。中断指向来自外部 I/O 控制器或设备到处理器的请求。在不牺牲性能的情况下处理这些事件是很困难的。在接下来的讨论中,我们不会区分这两者。我们将它们统称为例外。下面列出了此类例外的一些示例:
• I/O 设备请求
• 从用户程序调用 OS 服务
• 跟踪指令执行
• 断点
• 整数算术溢出
• FP 算术异常
• 页面错误
• 未对齐的内存访问
• 内存保护违规
• 使用未定义或未实现的指令
• 硬件故障
• 电源(检测)失败
异常有不同的特征。它们如下:
• 同步与异步
• 一些异常可能是同步的,而另一些可能是异步的。如果相同的异常发生在相同的地方, 相同的数据和内存分配,那么就是同步异常。
它们更难处理。
• CPU 和内存外部的设备会导致异步异常。它们可以在当前指令之后处理,因此比同步异常更容易。
• 用户请求与强制
• 某些例外可能是用户请求的,而不是自动的。此类异常是可预测的,可以在当前指令之后处理。
• 强制异常通常由硬件引发,不受用户程序控制。他们更难处理。
• 用户可屏蔽与不可屏蔽
• 异常可以是可屏蔽的或不可屏蔽的。它们可以被用户任务屏蔽或取消屏蔽。这决定了硬件是否响应异常。您可能有启用或禁用异常的说明。
• 指令内 vs 间指令
• 异常可能必须在指令内或指令之间处理。异常内通常是同步的,并且更难,因为指令必须停止和重新启动。硬件故障等灾难性异常通常会导致终止。
• 可以在两条指令之间处理的异常更容易处理。
• 恢复与终止
• 有些异常可能导致程序在异常之后继续,有些则可能导致程序终止。如果我们必须重新启动,事情就会复杂得多。
• 导致终止的异常要容易得多,因为我们只需要终止而不需要恢复原始状态。
因此,指令中发生的异常和必须可重启的异常处理起来要困难得多。
异常只是另一种形式的控制风险。例如,考虑在 EX 阶段的 ADD 指令上发生溢出:
加 1 美元、2 美元、1 美元
我们要基本防止$1被写入,完成前面没有问题的指令,刷新ADD和后续指令,处理异常。这有点类似于错误预测的分支,我们可以使用许多相同的硬件。通常,一旦引发异常,我们会在下一个 IF 时强制将陷阱指令放入管道,并关闭故障指令和管道中所有后续指令的所有写入,直到捕获陷阱。这是通过在锁存器中放置零来完成的,从而防止任何状态更改,直到处理异常为止。异常处理例程保存出错指令的 PC,以便稍后从异常中返回。但是如果我们使用延迟分支,不可能用单个 PC 重新创建处理器的状态,因为流水线中的指令可能没有顺序相关。所以我们需要保存和恢复与分支延迟长度加一一样多的PC。这在图 15.1 中有图示
一旦异常被处理,在可重启异常的情况下,控制必须转移回原始程序。ISA 支持通过重新加载 PC 和重新启动指令流从异常中返回处理器的特殊指令。例如,MIPS 使用指令 RFE。
如果流水线可以被停止,以便在发生故障的指令之前的指令完成,并且可以从头重新启动之后的指令,那么流水线被称为有精确异常。 Generally, the instruction causing a problem is prevented from changing the state. But, for some exceptions, such as floating-point exceptions, the faulting instruction on some processors writes its result before the exception can be handled. In such cases, the hardware must be equipped to retrieve the source operands, even if the destination is identical to one of the source operands. Because floating-point operations may run for many cycles, it is highly likely that some other instruction may have written the source operands. To overcome this, many recent processors have introduced two modes of operation. One mode has precise exceptions and the other (fast or performance mode) does not. The precise exception mode is slower, since it allows less overlap among floating point instructions. In some high-performance CPUs, including Alpha 21064, Power2, and MIPS R8000, the precise mode is often much slower (> 10 times) and thus useful only for debugging of codes.
在查看了与异常相关的一般问题之后,让我们现在看看
特别是 MIPS 架构。MIPS 管道中可能发生的异常是:
• IF – 页错误、未对齐的内存访问、内存保护违规
• ID – 未定义或非法的操作码
• EX——算术异常
• MEM – 数据缺页、内存访问未对齐、内存保护违规
• WB – 无
在 MIPS 中,异常由系统控制协处理器 (CP0) 管理。它保存有问题或中断指令的 PC。一个称为异常程序计数器 (EPC) 的寄存器用于此目的。我们还应该知道异常的原因。这给出了问题的指示。MIPS 使用一个称为 Cause Register 的寄存器来记录异常的原因。让我们单独假设两种不同类型的异常,由一位标识 - 未定义指令 = 0 和算术溢出 = 1。为了处理这两个寄存器,我们需要添加两个控制信号EPCWrite和CauseWrite。此外,我们将需要一个 1 位的控制信号来适当地设置 Cause 寄存器的低位,例如信号IntCause. 在 MIPS 架构中,异常处理程序地址是 8000 0180。
处理异常的另一种方法是通过向量中断,其中处理程序地址由原因决定。在向量中断中,控制转移到的地址由异常的原因决定。例如,如果我们考虑两种不同类型的异常,我们可以将两个异常向量地址定义为未定义操作码:C0000000,溢出:C0000020。操作系统通过引发异常的地址知道异常的原因。总而言之,指令要么处理中断,要么跳转到真正的处理程序。此处理程序读取原因并将控制转移到相关处理程序,以确定所需的操作。如果是可重启异常,则采取纠正措施 EPC用于返回程序。否则,程序终止并报告错误。图 15.2 显示了 MIPS 流水线,其中添加了 EPC 和 Cause 寄存器,并将异常处理程序地址添加到为 PC 供电的多路复用器。
让我们看一个示例场景并讨论发生异常时 MIPS 管道中会发生什么。考虑以下代码片段并假设 add 指令在执行阶段引发异常。如图 15.3 所示。在第6个时钟周期,add指令在执行阶段,slt指令在decode阶段,lw指令在fetch阶段。
40 低于 11 美元、2 美元、4 美元
44 美元和 12 美元、2 美元、5 美元
48 美元或 13 美元、2 美元、6 美元
4C 加$1, $2, $1
50 slt 15 美元、6 美元、7 美元
54 磅 $16, 50($7)
一旦在执行阶段引发异常,气泡就会从导致问题的指令开始插入管道中,即在这种情况下添加。允许先前的指令正常进行。在下一个时钟周期,即第7个时钟周期,一条sw $25, 1000($0)指令进入流水线处理异常。如图 15.4 所示。
我们需要考虑的另一个复杂情况是多个异常可能同时发生,比如在 IF 和 MEM 阶段,并且异常可能会无序发生。较早阶段的指令,例如,IF 可能引发异常,然后 EX 阶段的指令可能引发异常。由于流水线与多条指令重叠,因此我们可能会同时出现多个异常,也可能出现乱序。但是,必须按顺序处理异常。通常,硬件维护一个状态向量,并在与该指令关联的状态向量中发布由给定指令引起的所有异常。当指令沿流水线向下移动时,会携带该异常状态向量。一旦在异常状态向量中设置了异常指示,任何可能导致写入数据值的控制信号都被关闭(这包括寄存器写入和存储器写入)。因为存储可能会在 MEM 期间导致异常,所以硬件必须准备好防止存储在引发异常时完成。当一条指令进入 WB 阶段时,会检查异常状态向量。如果有任何异常发布,它们将按照它们在非流水线处理器上及时发生的顺序进行处理。因此,硬件总是从最早的指令开始处理异常,如果是终止异常,则刷新后续指令。这就是维护精确异常的方式。硬件必须准备好防止存储在引发异常时完成。当一条指令进入 WB 阶段时,会检查异常状态向量。如果有任何异常发布,它们将按照它们在非流水线处理器上及时发生的顺序进行处理。因此,硬件总是从最早的指令开始处理异常,如果是终止异常,则刷新后续指令。这就是维护精确异常的方式。硬件必须准备好防止存储在引发异常时完成。当一条指令进入 WB 阶段时,会检查异常状态向量。如果有任何异常发布,它们将按照它们在非流水线处理器上及时发生的顺序进行处理。因此,硬件总是从最早的指令开始处理异常,如果是终止异常,则刷新后续指令。这就是维护精确异常的方式。硬件总是从最早的指令开始处理异常,如果是终止异常,则刷新后续指令。这就是维护精确异常的方式。硬件总是从最早的指令开始处理异常,如果是终止异常,则刷新后续指令。这就是维护精确异常的方式。
However, in complex pipelines where multiple instructions are issued per cycle, or those that lead to Out-of-order completion because of long latency instructions, maintaining precise exceptions is difficult. In such cases, the pipeline can just be stopped and the status including the cause of the exception is saved. Once the control is transferred to the handler, the handler will determine which instruction(s) had exceptions and whether each instruction is to be completed or flushed. This may require manual completion. This simplifies the hardware, but the handler software becomes more complex.
除了异常引起的复杂性之外,ISA 还可能带来一些问题。在 MIPS 架构的情况下,所有指令都会写入寄存器文件(存储除外),并且只发生在最后阶段。但对于一些 ISA,事情可能会更复杂。例如,当支持自动递增寻址模式时,在指令中间发生寄存器写入。现在,如果指令因异常而中止,它将使处理器状态改变。虽然我们知道是哪条指令导致了异常,但如果没有额外的硬件支持,异常将是不精确的,因为指令将完成一半。在这种不精确的异常之后重新启动指令流是很困难的。在执行期间更新内存状态的指令也会出现类似的问题,例如 VAX 或 IBM 360 上的字符串复制操作。内存位置改变。为了可以中断和重新启动这些指令, 这些指令被定义为使用通用寄存器作为工作寄存器。因此,部分完成指令的状态总是在寄存器中,这些寄存器在发生异常时保存并在异常发生后恢复,从而允许指令继续。在VAX中额外记录了一条指令何时开始更新内存状态,这样当流水线重新启动时,CPU就知道是从指令的开头还是从指令的中间重新启动指令。IA-32 字符串指令也将寄存器用作工作存储器,因此保存和恢复寄存器即保存和恢复此类指令的状态。
由于条件代码,出现了另一个问题。许多处理器将条件代码隐式设置为指令的一部分。这种方法具有优势,因为条件代码将条件的评估与实际分支分离。
然而,隐式设置条件代码会导致在设置条件代码和分支之间调度任何流水线延迟时出现困难,因为大多数指令设置条件代码并且不能在条件评估和分支之间的延迟槽中使用。此外,在带有条件代码的处理器中,处理器必须决定何时修复分支条件。这涉及找出在分支之前最后一次设置条件代码的时间。在大多数具有隐式设置条件代码的处理器中,这是通过延迟分支条件评估直到所有先前的指令都有机会设置条件代码来完成的。实际上,条件代码必须被视为一个操作数,需要对带有分支的 RAW 危险进行危险检测,就像 MIPS 必须在寄存器上做的那样。
最后,我们将看看如何扩展 MIPS 流水线以处理浮点运算。典型的浮点流水线如图 15.5 所示。有多个执行单元,如 FP 加法器、FP 乘法等,它们具有不同的延迟。这些功能单元可能会或可能不会被流水线化。我们通常定义两个关于浮点管道的术语。该等待时间是产生结果的指令和使用该结果的指令之间居间的周期数。的起始或重复间隔是发出给定类型的两个操作之间必须经过的周期数。浮点流水线的结构需要引入额外的流水线寄存器(例如,A1/A2、A2/A3、A3/A4)并修改与这些寄存器的连接。ID/EX 寄存器必须扩展以将 ID 连接到 EX、DIV、M1 和 A1。
长延迟浮点指令导致更复杂的流水线。由于流水线中有更多指令,因此经常出现 RAW 危险。此外,由于这些浮点指令具有不同的延迟,多个指令可能同时完成,并且可能在一个周期内多次写入寄存器文件。这可能会导致结构性危害以及 WAW 危害。为了处理对寄存器文件的多次写入,我们需要增加端口数,或者在 ID 期间暂停其中一项写入,或者在 WB 期间暂停一项写入(暂停会传播)。在 ID 期间必须检测 WAW 危险,并且必须停止后面的指令。WAR 危险当然是不可能的,因为所有读取都发生在更早的时候。可变延迟指令以及因此无序完成也将导致不精确的异常。如果结果提前完成,您要么必须缓冲结果,要么保存更多管道状态,以便您可以返回到与您离开时完全相同的状态。
总而言之,我们已经讨论了管道中可能发生的不同类型的异常以及它们如何导致管道中的问题。我们已经讨论了 MIPS 架构如何处理它们。多个异常和无序异常使事情更加复杂。指令集的某些特征也可能使流水线复杂化。此外,浮点管道需要处理额外的复杂性。
网页链接/支持材料
计算机组织与设计——硬件/软件接口,David A. Patterson 和 John L. Hennessy,第 4 版,Morgan Kaufmann,Elsevier,2009 年。
计算机组织,Carl Hamacher、Zvonko Vranesic 和 Safwat Zaky,第 5 版,McGraw-Hill 高等教育,2011 年。